#include "Mesh.h"
#include "ResourceManager.h"
#include <iostream>

using namespace std;

namespace glow
{
	
Mesh::Mesh(MeshData *data, bool useVBO, GLenum update_type) :
	_useVBO(useVBO),
	_update_type(update_type),
	_data(data), 
	_texture(NULL),
	_pos(Vector3d(0,0,0)), 
	_axes(Vector3d(0,0,0)),
	_scale(Vector3f(1.,1.,1.)), 
	_angle(0)
{
	_vbo = new IBuffer*[4];
	for(int i=0; i<4; i++)
		_vbo[i] = NULL;
	if(data)
		create();
}

Mesh::~Mesh()
{
	for(int i=0; i<4; i++)
	{
		if(_vbo[i])
			delete _vbo[i];
	}
	delete[] _vbo;
	if(_data)
		_data->release();
}

void Mesh::setTexture(std::string filename)
{
	if(_texture)
	{
		_texture->release();
		_texture = NULL;
	}
	
	ResourceManager *rs = ResourceManager::getInstance();
	_texture = rs->get<Texture>(filename);
	
	if(!_texture)
		_texture = new Texture(filename);
}

void Mesh::genNormals(bool inv)
{
	if(!_data)
		return;
		
	float *vertices = _data->_vertices, *normals = _data->_normals;
	unsigned int *indices = _data->_index;
	unsigned int nbVertices = _data->_nb_vertices, nbIndices = _data->_nb_indices;
	
	if(!vertices || !indices)
		return;
	if(normals)
		delete[] normals;

	normals = new float[nbVertices*3];

	float v1[3], v2[3], v3[3], out[3];
	unsigned int aaux;
	
	for(unsigned int i=0 ; i<nbVertices*3; i++)
		normals[i] = 0;
	for(unsigned int i=0 ; i<nbIndices*3; i+=3)
    {
		aaux = indices[i];
		v1[0] = vertices[aaux*3];
		v1[1] = vertices[aaux*3+1];
		v1[2] = vertices[aaux*3+2];
		
		aaux = indices[i+1];
		v2[0] = vertices[aaux*3];
		v2[1] = vertices[aaux*3+1];
		v2[2] = vertices[aaux*3+2];

		aaux = indices[i+2];
		v3[0] = vertices[aaux*3];
		v3[1] = vertices[aaux*3+1];
		v3[2] = vertices[aaux*3+2];

		createnormal(v1, v2, v3, out) ;
		addnormal((normals+*(indices+i)*3),out);
		addnormal((normals+*(indices+i+1)*3),out);
		addnormal((normals+*(indices+i+2)*3),out);
    }
    
    if(inv)
    {
		for(unsigned int i=0; i<nbVertices*3; i++)
			normals[i] = normals[i]*-1;
	}
	
	_data->_normals = normals;
	_data->_nb_normals = _data->_nb_vertices;
	if(_data->_normals && _useVBO)
	{
		if(_vbo[1])
			delete[] _vbo[1];
		_vbo[1] = new VBO<STREAM, GL_NORMAL_ARRAY, GL_ARRAY_BUFFER>();
		_vbo[1]->push(_data->_normals, _data->_nb_normals*3*sizeof(float));
	}
}

/* observateurs */
const Vector3d &Mesh::getPos() const { return _pos; }
const Vector3d &Mesh::getAxes() const { return _axes; }
const Vector3f &Mesh::getScale() const { return _scale; }
float Mesh::getAngle() const { return _angle; }

const float *Mesh::getVertices() const { return _data->_vertices; }
const unsigned int *Mesh::getIndex() const { return _data->_index; }
const float *Mesh::getTexCoords() const { return _data->_texcoords; }
const float *Mesh::getNormals() const { return _data->_normals; }

unsigned int Mesh::getNbVertices() const { return _data->_nb_vertices; }
unsigned int Mesh::getNbIndex() const { return _data->_nb_indices;}
unsigned int Mesh::getNbTexCoord() const { return _data->_nb_texcoords; }
unsigned int Mesh::getNbNormals() const { return _data->_nb_normals; }

/* accesseurs */
void Mesh::setPos(const Vector3d &pos)
{
	_pos = pos;
}

void Mesh::setAngle(float angle, const Vector3d &axes)
{
	_angle = angle;
	_axes = axes;
}

void Mesh::setScale(const Vector3f &scale)
{
	_scale = scale;
}

void Mesh::setAmbient(const Color4f &color)
{
  _m.setAmbient(color);
}

void Mesh::setDiffuse(const Color4f &color)
{    
  _m.setDiffuse(color);
}

void Mesh::setSpecular(const Color4f &color)
{
  _m.setSpecular(color);
}

void Mesh::setShininess(int sh)
{
  _m.setShininess(sh);
}

void Mesh::draw() const
{
	glPushMatrix();
	
	glTranslatef(_pos.x, _pos.y,_pos.z);
	glRotatef(_angle,_axes.x, _axes.y, _axes.z);
	glScalef(_scale.x,_scale.y,_scale.z);
		
	_m.activate();

	if(!_data) return ;
	
	if(!_useVBO)
	{
		if(_data->_vertices)
		{    
			glEnableClientState(GL_VERTEX_ARRAY);
			glVertexPointer(3,GL_FLOAT,0,_data->_vertices);
		}
		
		if(_data->_normals)
		{
			glEnableClientState(GL_NORMAL_ARRAY);
			glNormalPointer(GL_FLOAT,0,_data->_normals);
		}
		
		if(_data->_texcoords)
		{   
			if(_texture)
				glEnable(GL_TEXTURE_2D);
			glEnableClientState(GL_TEXTURE_COORD_ARRAY);
			glTexCoordPointer(2,GL_FLOAT,0,_data->_texcoords);
		}

		glDrawElements(GL_TRIANGLES, _data->_nb_indices*3, GL_UNSIGNED_INT, _data->_index);

		if(_data->_vertices) glDisableClientState(GL_VERTEX_ARRAY);
		if(_data->_normals) glDisableClientState(GL_NORMAL_ARRAY);
		if(_data->_texcoords) glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		glDisable(GL_TEXTURE_2D);
	}
	else
	{		
		if(_vbo[0])
		{
			glBindBuffer(GL_ARRAY_BUFFER, _vbo[0]->getGLID());
			glVertexPointer(3, GL_FLOAT, 0, BUFFER_OFFSET(0));
			glEnableClientState(GL_VERTEX_ARRAY);
		}
		if(_vbo[1])
		{
			glBindBuffer(GL_ARRAY_BUFFER, _vbo[1]->getGLID());
			glNormalPointer(GL_FLOAT, 0, BUFFER_OFFSET(0));
			glEnableClientState(GL_NORMAL_ARRAY);
		}
		if(_vbo[2])
		{
			if(_texture)
				glEnable(GL_TEXTURE_2D);
			glBindBuffer(GL_ARRAY_BUFFER, _vbo[2]->getGLID());
			glTexCoordPointer(2, GL_FLOAT, 0, BUFFER_OFFSET(0));
			glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		}
		if(_vbo[3])
		{
			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _vbo[3]->getGLID());
			glIndexPointer(GL_UNSIGNED_INT, 0, BUFFER_OFFSET(0));
			glEnableClientState(GL_INDEX_ARRAY);
		}	
		glDrawElements(GL_TRIANGLES, _data->_nb_indices*3, GL_UNSIGNED_INT, BUFFER_OFFSET(0));

		glDisableClientState(GL_VERTEX_ARRAY);
		glDisableClientState(GL_NORMAL_ARRAY);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		glDisableClientState(GL_INDEX_ARRAY);
		glDisable(GL_TEXTURE_2D);
		glBindBufferARB(GL_ARRAY_BUFFER, 0);
		glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, 0);
	}
	glPopMatrix();
  }
  
  void Mesh::drawNormals() const
  {
	  if(_data->_normals)
	  {
		  glColor3ub(255, 0, 0);
		  glBegin(GL_LINES);
		  for(unsigned int i=0; i<_data->_nb_normals*3; i+=3)
		  {
			  glVertex3d(_data->_vertices[i], _data->_vertices[i+1], _data->_vertices[i+2]);
			  glVertex3d(_data->_vertices[i]+_data->_normals[i]/3, _data->_vertices[i+1]+_data->_normals[i+1]/3, _data->_vertices[i+2]+_data->_normals[i+2]/3);
		  }
		  glEnd();
		  glColor3ub(255, 255, 255);
	  }
  }

	void Mesh::create()
	{
		if(_useVBO)
		{
			if(_data->_vertices)
			{
				_vbo[0] = new VBO<STREAM, GL_VERTEX_ARRAY, GL_ARRAY_BUFFER>();
				_vbo[0]->push(_data->_vertices, _data->_nb_vertices*3*sizeof(float));
			}
			if(_data->_normals)
			{
				_vbo[1] = new VBO<STREAM, GL_NORMAL_ARRAY, GL_ARRAY_BUFFER>();
				_vbo[1]->push(_data->_vertices, _data->_nb_normals*3*sizeof(float));
			}
			if(_data->_texcoords)
			{
				_vbo[2] = new VBO<STREAM, GL_TEXTURE_COORD_ARRAY, GL_ARRAY_BUFFER>();
				_vbo[2]->push(_data->_texcoords, _data->_nb_texcoords*3*sizeof(float));
			}
			if(_data->_index)
			{
				_vbo[3] = new VBO<STREAM, GL_INDEX_ARRAY, GL_ELEMENT_ARRAY_BUFFER>();
				_vbo[3]->push(_data->_index, _data->_nb_indices*3*sizeof(unsigned int));
			}
		}
	}
	
void Mesh::normcrossprod(float v1[3], float v2[3], float out[3]) 
{ 
	out[0] = v1[1]*v2[2] - v1[2]*v2[1]; 
	out[1] = v1[2]*v2[0] - v1[0]*v2[2]; 
	out[2] = v1[0]*v2[1] - v1[1]*v2[0]; 
	normalize(out); 
} 


void Mesh::createnormal(float *v1, float *v2, float *v3,float *out) 
{ 
	GLfloat d1[3], d2[3], norm[3]; 
	for (int j = 0; j < 3; j++)
    {
		d1[j] = v2[j] - v1[j];    
		d2[j] = v3[j] - v1[j];    
    }
	normcrossprod(d1, d2, norm);
	for (int j = 0; j < 3; j++)
		out[j] = norm[j];
}

void Mesh::addnormal(float *n, float *newnorm) 
{ 
	GLfloat norm[3];
	for(int j = 0; j < 3; j++)
		norm[j] = n[j] + newnorm[j];    
	normalize(norm);
	for(int j = 0; j < 3; j++)
		n[j] = norm[j];
}

void Mesh::normalize(float v[3])
{
	GLfloat d = sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]);
	if (d == 0.0)  
		return; 
	v[0] /= d; v[1] /= d; v[2] /= d; 
} 

}
